package com.qfmy.common.utils;

import com.alibaba.fastjson.JSONObject;
import com.qfmy.common.pojo.TestUser;
import com.qfmy.common.pojo.TokenVo;
import com.qfmy.core.constant.CommonConstants;
import com.qfmy.core.pojo.entity.ErrorCode;
import com.qfmy.core.pojo.exception.CustomException;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.UUID;

/**
 * @ClassName: JwtUtils
 * @author： zhengjt
 * @data： 2022/6/10 15:21
 * @version：1.0
 * @描述： JWT工具类
 */
@Slf4j
public class JwtUtils {

    /**
     * 加密盐
     */
    public static final String SECRET = "sky_base_fantasy_dictionary";

    /**
     * token的Claim生成方式
     */
    private static final String CLAIM = "Claim";

    public static void main(String[] args) throws CustomException {
        String type = "Json";
//        String type = CLAIM;
        TokenVo object = new TokenVo();
        object.setUserId("1");
        object.setUserName("admin");
        object.setUserNick("测试-管理员");
        object.setDeptId("000000001");
        object.setDeptName("测试部门");
        String token = createToken(object, type);
        System.out.println(" ---------------------------------------------- ");
        System.out.println(token);
        System.out.println(" ---------------------------------------------- ");
        System.out.println(parseToken(token));
        System.out.println(parseToken(token).getId());
        System.err.println(" ---------------------------------------------- ");
        Long expiration = parseToken(token).getExpiration().getTime();
        Long nowTime = System.currentTimeMillis() + 60 * 60 * 1000L;
        Long lastTime =  4 * 60 * 60 * 1000L;
        System.out.println(expiration - nowTime);
        System.out.println(lastTime);
        System.out.println(expiration - nowTime < lastTime);
        System.out.println(" ---------------------------------------------- ");
        System.out.println(getTokenUser(token, type));
        System.out.println(" ---------------------------------------------- ");

    }

    /**
     * 创建token
     *
     * @param tokenVo 需要创建token的对象
     * @param type    生成token的方式：
     *                Claim：使用Claim需要确保该对象里的所有参数不可有缺失，即所有属性都需要为必填信息；
     *                Json： 使用json限制较少，可所有参数都为null，即所有参数都不为必填项；
     * @return token字符串
     */
    public static String createToken(TokenVo tokenVo, String type) {
        if (CLAIM.equals(type)) {
            return createTokenClaim(tokenVo);
        }
        return createTokenJson(tokenVo);
    }

    /**
     * 校验token是否正确
     *
     * @param token 需要校验的token
     * @return 正确输出true，错误输出false
     */
    public static boolean checkToken(String token) throws CustomException {
        try {
            Claims claims = parseToken(token);
            if (claims != null) {
                return true;
            }
        } catch (Exception e) {
            if (e instanceof CustomException){
                throw e;
            }
            return false;
        }
        return false;
    }

    /**
     * 解析Token，获取TokenVo对象
     *
     * @param token 需要解析的token
     * @param type  token 生成的方式
     * @return TokenVo对象
     * @throws CustomException 自定义解析对象
     */
    public static TokenVo getTokenUser(String token, String type) throws CustomException {
        if (CLAIM.equals(type)) {
            return getTokenUserClaim(token);
        }
        return getTokenUserJson(token);
    }

    /**
     * 获取tokenId
     *
     * @param token 需要获取id的token字符串
     * @return  tokenId
     * @throws CustomException
     */
    public static String getTokenId(String token) throws CustomException{
        return parseToken(token).getId();
    }

    //  ------------------ 对内方法START ---------------------

    //  ------------------ 生成token ---------------------

    /**
     * 创建Token，取出对象的各项参数，各项存入
     *
     * @param tokenVo 需要创建token的对象
     * @return token字符串
     */
    private static String createTokenClaim(TokenVo tokenVo) {
        //  token过期时间
        Date expirationDate = new Date(System.currentTimeMillis() + CommonConstants.TOKEN_KEEP_TIME);
        return Jwts.builder().setHeaderParam("typ", "JWT")
                .setHeaderParam("alg", "HS256")
                //  设置签名用户
                .setSubject("qfmy")
                //  token创建时间
                .setIssuedAt(new Date())
                //  token过期时间
                .setExpiration(expirationDate)
                //  设置token参数信息
                .claim("userId", tokenVo.getUserId())
                .claim("userName", tokenVo.getUserName())
                .claim("userNick", tokenVo.getUserNick())
                .claim("deptId", tokenVo.getDeptId())
                .claim("deptName", tokenVo.getDeptName())
                .claim("orgId", tokenVo.getOrgId())
                .claim("orgName", tokenVo.getOrgName())
                .claim("corpId", tokenVo.getCorpId())
                .claim("corpName", tokenVo.getCorpName())
                .setId(tokenVo.getUuid())
                //  签名设置
                .signWith(SignatureAlgorithm.HS256, SECRET)
                .compact();
    }

    /**
     * 创建Token，对象转JSON字符串存Token
     *
     * @param tokenVo 需要创建token的对象
     * @return token字符串
     */
    private static String createTokenJson(TokenVo tokenVo) {
        //  token过期时间
        Date expirationDate = new Date(System.currentTimeMillis() + CommonConstants.TOKEN_KEEP_TIME);
        String jsonString = JSONObject.toJSONString(tokenVo);
        return Jwts.builder()
                .setHeaderParam("typ", "JWT")
                .setHeaderParam("alg", "HS256")
                //  设置签名用户
                .setSubject("qfmy")
                //  token创建时间
                .setIssuedAt(new Date())
                //  token过期时间
                .setExpiration(expirationDate)
                //  设置token参数信息
                .claim("tokenObject", jsonString)
                .setId(tokenVo.getUuid())
                //  签名设置
                .signWith(SignatureAlgorithm.HS256, SECRET)
                .compact();
    }

    //  ------------------ 解析token ---------------------

    /**
     * 解析token信息
     *
     * @param token token字符串
     * @return
     */
    private static Claims parseToken(String token) throws CustomException {
        try {
            return Jwts.parser()
                    .setSigningKey(SECRET)
                    .parseClaimsJws(token)
                    .getBody();
        } catch (Exception e) {
            //  token过期抛出ExpiredJwtException异常
            if (e instanceof ExpiredJwtException) {
                throw new CustomException(ErrorCode.TOKEN_EXPIRED);
            }
            log.error("解析token信息异常： {}", e.getMessage());
            throw new CustomException(ErrorCode.TOKEN_PARSE_ERROR);
        }
    }

    //  ------------------ 转换token ---------------------

    /**
     * 解析token获取TokenVo对象
     *
     * @param token token信息
     * @return TokenVo对象
     * @throws CustomException 自定义异常信息
     */
    private static TokenVo getTokenUserClaim(String token) throws CustomException {
        //  解析token信息
        Claims claims = parseToken(token);

        TokenVo tokenVo = new TokenVo();

        Class<TokenVo> clazz = TokenVo.class;
        Field[] fields = clazz.getDeclaredFields();

        try {
            for (Field field : fields) {
                field.setAccessible(true);
                String setMethodName = "set" + field.getName().substring(0, 1).toUpperCase() + field.getName().substring(1);
                log.info("setMethodName = {}", setMethodName);
                Method method = clazz.getMethod(setMethodName, field.getType());
                log.info(" method : {}", method.toString());
                Object value = claims.get(field.getName(), field.getType());
                log.info("Object value : {}", value.toString());
                if (value != null) {
                    method.invoke(tokenVo, value);
                }
            }
        } catch (Exception e) {
            log.error("Claim token转对象异常： {}", e.getMessage());
            throw new CustomException(ErrorCode.TOKEN_PARSE_ERROR);
        }

        return tokenVo;
    }

    /**
     * 解析token获取TokenVo对象，生成时使用json方式
     *
     * @param token token信息
     * @return TokenVo对象
     * @throws CustomException 自定义异常信息
     */
    private static TokenVo getTokenUserJson(String token) throws CustomException {
        //  解析token信息
        Claims claims = parseToken(token);
        //  获取存入token的json字符串
        String tokenJson = (String) claims.get("tokenObject");
        //  json字符串转换成对象
        return JSONObject.parseObject(tokenJson, TokenVo.class);
    }

    //  ------------------ END ---------------------


}
